package utils

import (
	"context"
	"errors"
	"github.com/go-redsync/redsync/v4"
	"github.com/go-redsync/redsync/v4/redis/goredis/v9"
	"github.com/redis/go-redis/v9"
	"time"
)

//  分布式锁 使用例子
//	option := []redsync.Option{
//		redsync.WithTries(3),                           // 获取锁次数,最小为1，不能小于1次
//		redsync.WithRetryDelay(500 * time.Millisecond), // 每次 获取锁 的等待时间
//		redsync.WithExpiry(8 * time.Second),
//	}
//
//	if err := gin_frame.RedisSync.Handler("huangzhiin", func() error{
//		// 在这里执行需要锁的工作
//		time.Sleep(30 * time.Second)
//      retrun nil
//	}, true, option...); err != nil {
//		fmt.Println(err.Error()) // 一般未能获取到锁
//	}

// InitRedsync 初始化 redis 分布式锁
func InitRedsync(address, password string, db int) (*RedsyncClient, error) {
	// 创建一个redis的客户端连接
	redisClient := redis.NewClient(&redis.Options{
		Addr: address, // 要连接的redis IP:port
		//Username: "default",
		Password: password, // redis 密码
		DB:       db,       // 要连接的redis 库
		PoolSize: 100,      //连接池大小
	})

	if _, err := redisClient.Ping(context.Background()).Result(); err != nil {
		return nil, errors.New("分布式锁 redis连接失败：" + err.Error())
	}

	// 创建redsync的客户端连接池
	pool := goredis.NewPool(redisClient)

	return &RedsyncClient{
		redsync: redsync.New(pool), // 创建redsync实例
	}, nil
}

type RedsyncClient struct {
	redsync *redsync.Redsync
}

func (rn RedsyncClient) Handler(lockName string, handler func() error, needLockExtend bool, option ...redsync.Option) error {
	//needLockExtend 是否需要进行自动续租，避免 handler执行时间 大于 锁的过期时间(默认8秒)，导致异常
	mutex := rn.redsync.NewMutex(lockName, option...)

	// 获取互斥锁的锁，这个操作成功后，没有人能获取同一个锁（同一个互斥锁名字）直到我们解锁它
	if err := mutex.Lock(); err == nil { // 这将一直阻塞等待获取到锁，等待时间为：参考 option
		defer mutex.Unlock() // 释放锁，这样其他进程或线程就可以获取锁了,如果不手动释放锁的话，系统会根据lockname的过期时间自动释放

		//对 锁 进行续租
		if needLockExtend {
			//创建一个channel，用来通知续租goroutine任务已经完成
			extendChannl := make(chan bool)
			defer close(extendChannl)       //通知goRoutine停止续租
			go func(mutex *redsync.Mutex) { // 开启一个goroutine，周期性地续租锁
				mutexExpiry := mutex.Until().Unix() - time.Now().Unix()              //计算出 锁的过期时间
				ticker := time.NewTicker(time.Duration(mutexExpiry-3) * time.Second) // 定时器(在 锁的过期时间 之前 3秒)
				defer ticker.Stop()
				for {
					select {
					case <-ticker.C:
						_, _ = mutex.Extend() //进行锁的续租
					case <-extendChannl:
						return
					}
				}
			}(mutex)
		}

		// 在这里执行需要锁的工作
		return handler()
	} else {
		return errors.New("获取锁[ " + lockName + " ]失败：" + err.Error())
	}
}
